package com.atguigu.gmall.cart.service.impl;

import com.atguigu.gmall.cart.mapper.CartInfoMapper;
import com.atguigu.gmall.cart.service.CartAsyncService;
import com.atguigu.gmall.cart.service.CartService;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.util.DateUtil;
import com.atguigu.gmall.model.cart.CartInfo;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.sql.Timestamp;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author mqx
 * @date 2020-11-16 15:28:32
 */
@Service
public class CartServiceImpl implements CartService {

    @Autowired
    private CartInfoMapper cartInfoMapper;

    @Autowired
    private ProductFeignClient productFeignClient;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private CartAsyncService cartAsyncService;

    @Override
    public void addToCart(Long skuId, String userId, Integer skuNum) {
        //  数据类型+key！ Hash ，    hset(key,field,value); hget(key,field);
        //  user:userId:cart    field=skuId,  value=cartInfo.toString();
        String cartKey = getCartKey(userId);
        /*
        mysql + redis;
        先同步：    mysql ，redis

        1.  添加购物车的时候，需要判断购物车中是否有该商品
            select * from cart_info where skuId = skuId and userId = userId;
            true:   数量相加
            false:  直接添加
            mnysql + redis
        2.  需要设置一下缓存购物车的过期时间
         */
        //  判断缓存中是否有该购物车的key
        if (!redisTemplate.hasKey(cartKey)){
            //  查询数据库，将数据放入缓存！
            this.loadCartCache(userId);
        }

        //  当 this.loadCartCache(userId); 执行的时候，说明缓存中一定有数据了！在此查询缓存！
        //  当 this.loadCartCache(userId); 这段代码没有执行 ，    缓存中有数据！
        //  hget(key,field); user:userId:cart    field=skuId,  value=cartInfo.toString();
        CartInfo cartInfoExist = (CartInfo) redisTemplate.boundHashOps(cartKey).get(skuId.toString());

        //  查询数据库,select * from cart_info where user_id = userId and sku_id = skuId;
//        QueryWrapper<CartInfo> cartInfoQueryWrapper = new QueryWrapper<>();
//        cartInfoQueryWrapper.eq("user_id",userId).eq("sku_id",skuId);
//        CartInfo cartInfoExist = cartInfoMapper.selectOne(cartInfoQueryWrapper);

        //  判断：
        if (cartInfoExist!=null){
            //  购物车中有当前商品！
            cartInfoExist.setSkuNum(cartInfoExist.getSkuNum()+skuNum);

            //  获取到商品的实时价格给 skuPrice = skuInfo.priec();
            cartInfoExist.setSkuPrice(productFeignClient.getSkuPrice(skuId));

            //  赋值更新时间
            cartInfoExist.setUpdateTime(new Timestamp(new Date().getTime()));

            //  cartInfoMapper.updateById(cartInfoExist);
            //  update cart_info set sku_num = ? where id = ? 要想更新成功id 是不能为空！
            cartAsyncService.updateCartInfo(cartInfoExist);

            //  操作redis

        }else{
            SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
            CartInfo cartInfo = new CartInfo();

            cartInfo.setUserId(userId);
            cartInfo.setSkuId(skuId);
            //  添加的时候其实就是最新价格了。
            cartInfo.setCartPrice(skuInfo.getPrice());
            cartInfo.setSkuPrice(skuInfo.getPrice());
            cartInfo.setSkuNum(skuNum);
            cartInfo.setImgUrl(skuInfo.getSkuDefaultImg());
            cartInfo.setSkuName(skuInfo.getSkuName());
            cartInfo.setCreateTime(new Timestamp(new Date().getTime()));
            cartInfo.setUpdateTime(new Timestamp(new Date().getTime()));

            //  添加到数据库
            //  cartInfoMapper.insert(cartInfo);
            cartAsyncService.saveCartInfo(cartInfo);
            cartInfoExist = cartInfo;
            //  修改redis!
        }
        //  放入数据：
        //  redisTemplate.opsForHash().put(cartKey,skuId,cartInfoExist);
        redisTemplate.boundHashOps(cartKey).put(skuId.toString(),cartInfoExist);
        //  给过期时间
        setCartExpire(cartKey);
    }

    @Override
    public List<CartInfo> getCartList(String userId, String userTempId) {
        List<CartInfo> cartInfoList = new ArrayList<>();
        //  用户未登录
        if (StringUtils.isEmpty(userId)){
            //  查询临时购物车
            cartInfoList = getCartList(userTempId);
        }

        //  用户已经登录
        if (!StringUtils.isEmpty(userId)){
            //  合并购物车
            //  先有未登录购物车数据
            List<CartInfo> cartInfoNoLoginList = null;
            if(!StringUtils.isEmpty(userTempId)){
               cartInfoNoLoginList = getCartList(userTempId);
            }
            //
            if(!CollectionUtils.isEmpty(cartInfoNoLoginList)){
                //  调用合并购物车的方法
                //  这个方法中有异步！操作数据库！
                cartInfoList =  mergeToCartList(cartInfoNoLoginList,userId);
                //  将合并的数据进行汇总查询出来！ 37,38,39。 canal 能够解决！
                //  mysql + redis 必然会出现某一时刻的脏数据。只要动了数据库，那么我们就需要删除缓存！
                //  删除未登录购物车
                this.deleteCartList(userTempId);
            }
            //  如果未登录购物车数据为空则直接查询登录的
            if (CollectionUtils.isEmpty(cartInfoNoLoginList) || StringUtils.isEmpty(userTempId)){
                //  查询登录的
                cartInfoList = getCartList(userId);
            }
        }
        return cartInfoList;
    }

    @Override
    public void checkCart(String userId, Integer isChecked, Long skuId) {
        //  mysql
        cartAsyncService.checkCart(userId,isChecked,skuId);
        //  redis
        String cartKey = this.getCartKey(userId);
        //  hset(key,field,value);
        CartInfo cartInfoRedis = (CartInfo) redisTemplate.boundHashOps(cartKey).get(skuId.toString());
        //  修改选中状态
        cartInfoRedis.setIsChecked(isChecked);
        //  将修改之后的数据存储缓存！
        redisTemplate.boundHashOps(cartKey).put(skuId.toString(),cartInfoRedis);
        //  过期时间：   可以有，可以无！

    }

    @Override
    public void deleteCart(Long skuId, String userId) {
        //  删除缓存+ 删除数据库
        cartAsyncService.deleteCartInfo(userId,skuId);

        //  必须先获取到缓存的key
        String cartKey = this.getCartKey(userId);
        //  删除 在缓存中看 key = user:userId:cart ,field = skuId
        if (redisTemplate.boundHashOps(cartKey).hasKey(skuId.toString())){
            //  redisTemplate.delete(cartKey);  删除整个购物车了！
            redisTemplate.boundHashOps(cartKey).delete(skuId.toString());
        }
    }

    @Override
    public List<CartInfo> getCartCheckedList(String userId) {
        List<CartInfo> cartInfoList = new ArrayList<>();
        //  根据用户Id 查询购物车列表，关键是查询谁? mysql or redis ?
        //  查询缓存！可以直接查询redis！
        //  获取购物车的key
        String cartKey = this.getCartKey(userId);
        //  获取到所有数据
        List<CartInfo> cartInfoRedisList = redisTemplate.boundHashOps(cartKey).values();
        if (!CollectionUtils.isEmpty(cartInfoRedisList)){
            //  细节问题：需要获取选中商品
            for (CartInfo cartInfo : cartInfoRedisList) {

                if (cartInfo.getIsChecked().intValue()==1){
                    cartInfoList.add(cartInfo);
                }
            }
        }

        //  返回数据
        return cartInfoList;
    }

    //  删除购物车
    private void deleteCartList(String userTempId) {
        // 删除数据库，删除redis
        cartAsyncService.deleteCartInfo(userTempId);
        //  删除缓存
        String cartKey = this.getCartKey(userTempId);
        //  如果有key 则删除
        if (redisTemplate.hasKey(cartKey)){
            redisTemplate.delete(cartKey);
        }

    }


    /**
     * 合并购物车
     * @param cartInfoNoLoginList
     * @param userId
     * @return
     */
    private List<CartInfo> mergeToCartList(List<CartInfo> cartInfoNoLoginList, String userId) {
         /*
        demo1:
        登录：
            37 1
            38 1
        未登录：
            37 1
            38 1
            39 1
        合并之后的数据
            37 2
            38 2
            39 1
     demo2:
         未登录：
            37 1
            38 1
            39 1
            40 1
          合并之后的数据
            37 1
            38 1
            39 1
            40 1
         */
        //  登录购物车数据
        List<CartInfo> cartLoginList = this.getCartList(userId);

        //  未登录购物车数据 ： cartInfoNoLoginList
        //  合并条件是什么？ skuId 相同
        //  将cartLoginList 登录购物车集合 转换为 map map的key = skuId  | value = cartInfo
        Map<Long, CartInfo> cartLoginMap = cartLoginList.stream().collect(Collectors.toMap(CartInfo::getSkuId, cartInfo -> cartInfo));

        //  判断是否有包含关系 ，至少有一个未登录购物车的skuId
        for (CartInfo cartInfoNoLogin : cartInfoNoLoginList) {
            //  未登录购物车中的skuId
            Long skuId = cartInfoNoLogin.getSkuId();
            //  判断两个购物车的skuId 相同
            if(cartLoginMap.containsKey(skuId)){
                //  处理的是37，38
                //  开始做数量合并。
                //  登录购物车的skuNum + 未登录购物车的skuNum
                //  获取到登录的对象
                CartInfo cartInfoLogin = cartLoginMap.get(skuId);
                cartInfoLogin.setSkuNum(cartInfoNoLogin.getSkuNum()+cartInfoLogin.getSkuNum());

                //  细节处理： 选择状态
                if(cartInfoNoLogin.getIsChecked().intValue()==1){
                    //  表示只修改登录条件为0的，未选中的商品！
                    if(cartInfoLogin.getIsChecked().intValue()!=1){
                        cartInfoLogin.setIsChecked(1);
                    }
                    // cartInfoLogin.setIsChecked(1);
                }
                //  更新数据
                //  当前 cartInfoLogin 中是否满足更新条件 是否有userId and skuId;
                //  异步更新！
                // cartAsyncService.updateCartInfo(cartInfoLogin);
                cartInfoMapper.updateById(cartInfoLogin);

            }else {
                //  处理 39
                //  将未登录的临时用户Id，改为真是的用户Id
                cartInfoNoLogin.setUserId(userId);
                //  更新数据
                // cartAsyncService.saveCartInfo(cartInfoNoLogin);
                cartInfoMapper.insert(cartInfoNoLogin);

            }
        }
        //  删除缓存的数据！ 并不好：采用睡眠方式， 采用 同步缓存，异步数据库！ 目的：只是想要速度快！
        //  采用同步！   或者： 同步数据库，异步缓存！
        //        String cartKey = this.getCartKey(userId);
        //        //  如果有key 则删除
        //        if (redisTemplate.hasKey(cartKey)){
        //            redisTemplate.delete(cartKey);
        //        }

        List<CartInfo> cartInfoList = loadCartCache(userId);
        //  返回null；
        return cartInfoList;
    }

    //  根据用户Id 获取购物车列表
    private List<CartInfo> getCartList(String userId) {
        List<CartInfo> cartInfoList = new ArrayList<>();
        //  判断用户Id 是否为空！
        if (StringUtils.isEmpty(userId)){
            return cartInfoList;
        }
        //  查询过程先从缓存，缓存没有再从数据库并放入缓存！
        //  获取key   hget(key,field)
        String cartKey = getCartKey(userId);
        //  缓存中存储的value 都是CartInfo
        cartInfoList = redisTemplate.boundHashOps(cartKey).values();
        //  cartInfoList = redisTemplate.opsForHash().values(cartKey);
        if (!CollectionUtils.isEmpty(cartInfoList)){
            //  有排序规则？
            cartInfoList.sort(new Comparator<CartInfo>() {
                //  自定义比较器
                @Override
                public int compare(CartInfo o1, CartInfo o2) {
                    //  按照更新时间进行比较！
                    return DateUtil.truncatedCompareTo(o2.getUpdateTime(),o1.getUpdateTime(), Calendar.SECOND);
                }
            });
            return cartInfoList;
        }else {
            //  从数据库获取数据并放入缓存！
            cartInfoList = this.loadCartCache(userId);
            return cartInfoList;
        }
    }

    //  表示缓存中没有数据，从数据库中获取并添加到缓存！
    public List<CartInfo> loadCartCache(String userId) {
        //  select * from cart_info where user_id = userId;
        List<CartInfo> cartInfoList = cartInfoMapper.selectList(new QueryWrapper<CartInfo>().eq("user_id", userId));
        //  判断集合
        if (CollectionUtils.isEmpty(cartInfoList)){
            return cartInfoList;
        }
        //  如果不为空，需要做一个处理，将数据放入缓存！
        //  获取key
        String cartKey = getCartKey(userId);
        HashMap<String, Object> hashMap = new HashMap<>();
        //  循环遍历集合
        for (CartInfo cartInfo : cartInfoList) {
            //  细节问题：   给实时价格赋值
            cartInfo.setSkuPrice(productFeignClient.getSkuPrice(cartInfo.getSkuId()));
            //  修改添加时价格
            //  cartInfo.setCartPrice(productFeignClient.getSkuPrice(cartInfo.getSkuId()));

            hashMap.put(cartInfo.getSkuId().toString(),cartInfo);
            //  redisTemplate.opsForHash().put(cartKey,cartInfo.getSkuId().toString(),cartInfo);
        }

        //  将数据存储到缓存!
        redisTemplate.opsForHash().putAll(cartKey,hashMap);
        //  设置一个过期时间
        this.setCartExpire(cartKey);
        //  排序。。。。。

        //  返回数据
        return cartInfoList;
    }

    //  设置购物车过期时间
    private void setCartExpire(String cartKey) {
        redisTemplate.expire(cartKey, RedisConst.USER_CART_EXPIRE, TimeUnit.SECONDS);
    }

    //  获取购物车key
    private String getCartKey(String userId) {
       return RedisConst.USER_KEY_PREFIX+ userId+RedisConst.USER_CART_KEY_SUFFIX;
    }
}
